using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace MR_HKSUtility
{
    /**
     * Multimedia Retrieval
     * Heat Kernel Signature project
     * Frank Borgonjen, Joeri van der Lei & Kevin van der Lei
     * 27-10-2011
     * 
     * The OffReader class is a utility class containing methods to read model files in the .off format and
     * generate/write laplacian matrices. It also contains a method to colorize a .off file to represent 
     * a given set of heat kernel signature values.
     */
    public class OffReader
    {
        #region Variables

        string fileName;
        List<int>[] connectedVertices;
        int nrVertices;
        int nrFaces;

        #endregion

        #region Functions

        /**
         * Constructs an OffReader using the given filename. The filename should be in full path form.
         * @param fn The file name used by the OffReader.
         * @require fn != null
         */
        public OffReader(string fn)
        {
            this.fileName = fn;
        }

        /**
         * Saves a vertex to the list of connected vertices. Automatically checks for duplicate connections.
         */
        private void SaveToVertexList(int index, int connectedVertex)
        {
            if (!connectedVertices[index].Contains(connectedVertex))
            {
                connectedVertices[index].Add(connectedVertex);
            }
        }

        /**
         * Reads the OFF file given in the constructor and generates the list of connected vertices
         */
        public void ReadFile()
        {
            TextReader reader = new StreamReader(fileName);

            //skip first line
            string line = reader.ReadLine();

            //second line contains vertices and faces counts.
            line = reader.ReadLine();
            string[] split = line.Split(" ".ToCharArray());
            nrVertices = Convert.ToInt32(split[0]);
            nrFaces = Convert.ToInt32(split[1]);

            //skip all vertex descriptions
            for (int i = 0; i < nrVertices; i++)
            {
                reader.ReadLine();
            }

            line = reader.ReadLine();

            //construct the data array containing all connected vertices for each vertex
            connectedVertices = new List<int>[nrVertices];
            for (int i = 0; i < connectedVertices.Length; i++)
            {
                connectedVertices[i] = new List<int>();
            }

            //read all face lines
            while (line != null)
            {
                split = line.Split(" ".ToCharArray());
                int v1 = Convert.ToInt32(split[1]);
                int v2 = Convert.ToInt32(split[2]);
                int v3 = Convert.ToInt32(split[3]);
                SaveToVertexList(v1, v2);
                SaveToVertexList(v1, v3);
                SaveToVertexList(v2, v1);
                SaveToVertexList(v2, v3);
                SaveToVertexList(v3, v1);
                SaveToVertexList(v3, v2);

                //read new line
                line = reader.ReadLine();
            }

            reader.Close();
        }

        /**
         * Returns the number of vertices in the OFF file.
         */
        public int getNumberOfVertices()
        {
            return nrVertices;
        }

        /**
         * Returns the number of faces in the OFF file.
         */
        public int getNumberOfFaces()
        {
            return nrFaces;
        }

        /**
         * Writes basic information about the OFF file in the Console.
         */
        public void PrintInfo()
        {
            //print info of all vertices
            int totalConnections = 0;
            for (int i = 0; i < connectedVertices.Length; i++)
            {
                //Console.WriteLine("vertex " + i + " has " + connectedVertices[i].Count + " connections");
                totalConnections += connectedVertices[i].Count;
            }
            totalConnections = totalConnections / 2;
            Console.WriteLine("Number of vertices: " + getNumberOfVertices());
            Console.WriteLine("Number of faces: " + getNumberOfFaces());
            Console.WriteLine("Total connections: " + totalConnections);
        }

        /**
         * Generates a Laplacian matrix using the list of connected vertices.
         */
        public int[,] LaplacianMatrix()
        {
            int length = connectedVertices.Length;
            int[,] matrix = new int[length, length];
            for (int i = 0; i < length; i++)
            {
                for (int j = 0; j < length; j++)
                {
                    matrix[i, j] = i == j ? connectedVertices[i].Count : connectedVertices[i].Contains(j) ? -1 : 0;
                }
            }
            return matrix;
        }

        /**
         * Generates a Laplacian matrix using the list of connected vertices and writes it to a data file.
         * The name of the file consists of the original file name with all '.' characters removed and using
         * the .dat extension. E.g. "0002.null.0.OFF" becomes "0002null0OFF.dat".
         */
        public void LaplacianToFile()
        {
            // the file is written as the matrix is generated to prevent memory problems when reading very large .off files
            StreamWriter s = new StreamWriter(fileName.Replace(".", "") + ".dat");
            int length = connectedVertices.Length;
            for (int j = 0; j < length; j++)
            {
                
                for (int i = 0; i < length; i++)
                {
                    int val = (i == j ? connectedVertices[i].Count : connectedVertices[i].Contains(j) ? -1 : 0);
                    if (val != 0)
                    {
                        s.Write((i+1) + " " + (j+1) + " " + val);
                        if (j != length - 1 || i != length - 1)
                            s.WriteLine();
                    }
                }
            }
            s.Flush();
            s.Close();
        }

        /**
         * Colors the .off model file using the given array of heat kernel signature values. The colorized
         * model is stored in a separate .off file.
         * @param hks the array of heat kernel signature values associated with the model.
         * @param min the lowest value in the array of heat kernel signature values.
         * @param max the highest value in the array of heat kernel signature values.
         * @require hks != null
         */
        public void colorizeModel(double[] hks, double min, double max)
        {
            string offOutput = fileName.Replace(".off","_colorized.off");
            Stream stream = File.Open(offOutput, FileMode.Create);
            StreamWriter sw = new StreamWriter(stream);

            TextReader reader = new StreamReader(fileName);

            //skip first line
            string line = reader.ReadLine();
            sw.WriteLine("[C]OFF");

            //second line contains vertices and faces counts.
            line = reader.ReadLine();
            sw.WriteLine(line);
            string[] split = line.Split(" ".ToCharArray());
            nrVertices = Convert.ToInt32(split[0]);

            //skip all vertex descriptions
            int[] colors;
            for (int i = 0; i < hks.Length; i++)
            {
                colors = getColors(hks[i], min, max);
                Console.WriteLine(hks[i] + " " + colors[0] + " " + colors[1] + " " + colors[2]);
                sw.WriteLine(reader.ReadLine() + " " + colors[0] + " " + colors[1] + " " + colors[2]);
            }
            line = reader.ReadLine();
            sw.WriteLine(line);

            //read all face lines
            while (line != null)
            {
                //read new line
                line = reader.ReadLine();
                sw.WriteLine(line);
            }

            Console.WriteLine("min: " + min + " max: " + max);

            sw.Close();
            stream.Close();
            reader.Close();
        }

        /**
         * Turns the given hks value into a color.
         */
        private int[] getColors(double hksValue, double min, double max)
        {
            int[] colors = new int[3];
            hksValue = (hksValue - min) / (max - min);
            hksValue = Math.Max(0, Math.Min(1, hksValue));

            // red
            colors[0] = (int)Math.Round(255 * Math.Max(0, (2 * (hksValue - 0.5))));
            // green
            //colors[1] = (int)Math.Round(255 * 2 * Math.Max(0, 0.5 - Math.Abs(hksValue - 0.5)));
            // blue
            colors[2] = (int)Math.Round(255 * Math.Max(0, 1 - (2 * hksValue)));

            // green
            colors[1] = 255 - colors[0] - colors[2];

            //Console.WriteLine("R:" + colors[0] + " G:" + colors[1] + " B:" + colors[2]);

            return colors;
        }
        
        # endregion
    }
}
